package com.abel.bigwater.data.sanchuan

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.data.Bit32Array
import io.netty.buffer.ByteBuf
import org.joda.time.DateTime
import kotlin.experimental.and

/**
 * 查询周数据明细
示例（CMDID = 03） 表示
 * 查询从 7 月 2 日 12 时 0 分开始， 每隔 2.5 小时提取一个数据， 共提取 5 个数
据 若给出的起始时间与实际存储的起始时间不符， 则水表返回实际最接近而且
比起始时间大的存储时间， 并以该时间为基准回复数据。
比如查询的起始时间为 10 时 10 分， 而最接近该时间的数据存储时间分别是 10 时 0 分和 10时 20 分，
则水表返回起始时间为 10 时 20 分。
若给出的起始时间太靠前， 则水表返回的起始时间为表内最早一组数据的存储时间， 并以该时间点
为基准回复数据。 比如查询的起始时间为 10年 1 月1 日 0 时 0 分， 而水表最早存储的时间是 14年 1 月
1 日 10 时 20 分， 则水表返回起始时间为 14年 1 月1 日 10 时 20 分。
 */
class FetchData(_dataLen: Int?) : SanchuanData(_dataLen) {
    var cmdid: Short? = null
        get() = when {
            cmd != null -> cmd!!.code
            cmdWeekDetail != null -> cmdWeekDetail!!.back.toShort()
            else -> null
        }
        set(value) {
            field = value
            cmd = FetchCmdType.findByCode(field!!)
            cmdWeekDetail = if (cmd != null) null else FetchCmdWeekDetail(field!!.toInt())
        }

    var cmd: FetchCmdType? = null

    var cmdWeekDetail: FetchCmdWeekDetail? = null

    /**
     * StartTime（BCD）
    YYYYMMDDHHmm
    20 14 07 02 12 00
     */
    var startTimeStr: BcdString? = null

    var startTime: DateTime? = null
        get() = DateTime(startTimeStr!!.bcdArray!![0] * 100 + startTimeStr!!.bcdArray!![1],
                startTimeStr!!.bcdArray!![2].toInt(),
                startTimeStr!!.bcdArray!![3].toInt(),
                startTimeStr!!.bcdArray!![4].toInt(),
                startTimeStr!!.bcdArray!![5].toInt())
        set(value) {
            field = value
            startTimeStr = if (field == null) null else
                BcdString(byteArrayOf((field!!.year / 100).toByte(),
                        (field!!.year % 100).toByte(),
                        field!!.monthOfYear.toByte(),
                        field!!.dayOfMonth.toByte(),
                        field!!.hourOfDay.toByte(),
                        field!!.minuteOfHour.toByte()), true)
        }

    /**
     * TimeInterval（HEX），
    采集点间隔数
    时间间 隔单位的倍
    数， 0F 表示间隔（15*
    时间间隔单位） 分钟
     */
    var timeInterval: Byte? = null

    /**
     * PointNumber（HEX） 05 ， 最 大 不 超 过
    0x30(48)个
     */
    var pointNumber: Byte? = null

    /**
     * 计算数据长度，针对解析和打包
     */
    override fun computeDataLen(): Int {
        dataLen = when (cmd) {
            FetchCmdType.MonthBill -> 1
            FetchCmdType.DayBill -> 1
            else -> {
                1 + 6 + 1 + 1
            }
        }

        return dataLen!!
    }

    /**
     * 从当前位置写入打包字节流
     */
    override fun fillBuffer(buf: ByteBuf) {
        buf.alloc().buffer().apply {
            writeByte(cmdid!!.toInt())

            if (cmdWeekDetail != null) {
                assert(6 == startTimeStr?.size)
                writeBytes(startTimeStr!!.hexArray)

                writeByte(timeInterval!!.toInt())
                writeByte(pointNumber!!.toInt())
            }

            // transfer
            buf.writeBytes(this)
        }
    }

    /**
     * 解析字节流，从buf当前位置开始，并且消费其中的字节。
     */
    override fun <T : SanchuanData> parseBuffer(buf: ByteBuf): T? {
        buf.readBytes(dataLen!!).apply {
            val cmdid = readByte().toShort().and(0xFF)

            if (cmdWeekDetail != null) {
                // start-time
                startTimeStr = BcdString.create(this, 6)
                timeInterval = readByte()
                pointNumber = readByte()
            }
        }

        return this as T?
    }
}

/**
 * 1 CMDID 指令小类
N DAT 根据 CMDID 而不同
例：
03： 表示时间间隔单位
10 分钟， 查询日详细数
据明细
23：表示时间单位间隔为
5 分钟， 查询日详细数据
明细
 */
enum class FetchCmdType(_code: Short) {
    /**
     * 查询表内全部月结数据
     */
    MonthBill(0x01),
    /**
     * 查询表内全部日结数据
    最近 1 天的日结数据是在
    第 2 天 0 点存储的。 上报
    的日结数据即为该天 0 点
    的数据。 如 1 号的日结是
    1 号 0 点存储的数据。
    例：当前时间为 9 月 30日，
    则回复的起始时间为 8 月
    31 日， 最近一天的日结数
    据为 9 月 31 日 0 点数据。
     */
    DayBill(0x02);

    var code: Short = _code

    /**
     * 查询周数据明细: 0x03
     */

    companion object {
        fun findByCode(_code: Short): FetchCmdType? = FetchCmdType.values().find { it.code == _code }
    }
}

class FetchCmdWeekDetail(v1: Int) : Bit32Array(v1) {
    var periodType: DetailPeriodType = DetailPeriodType.TenMinutes
        get() = DetailPeriodType.findByCode(get(4, 7).toByte())!!
        set(value) {
            field = value
            set(4, 7, field.code.toInt())
        }

    var dataType: DetailDataType = DetailDataType.TotalForward
        get() = DetailDataType.findByCode(get(0, 3).toByte())!!
        set(value) {
            field = value
            set(0, 3, field.code.toInt())
        }

    /**
     * 取值范围（0-F）
    0： 10 分钟
    1： 1 分钟
    2： 5 分钟
    其它预留
     */
    enum class DetailPeriodType(_code: Byte, _minutes: Int) {
        TenMinutes(0, 10),
        OneMinutes(1, 1),
        FiveMinutes(2, 5);

        val code: Byte = _code

        val minutes = _minutes

        companion object {
            fun findByCode(_code: Byte) = DetailPeriodType.values().find { it.code == _code }
        }
    }

    /**
     * BIT3~0
    详细数据类型
    取值范围（3-F）
    3： 累计流量明细
    4： 瞬时流量明细
    5： 压力数据明细
    6： 反向流量明细
    7： 累计流量+瞬时流量+
    压力+反向流量明细
    其它预留
     */
    enum class DetailDataType(_code: Byte) {
        TotalForward(3),
        Flow(4),
        Pressure(5),
        Revert(6),
        AllFour(7);

        val code: Byte = _code

        companion object {
            fun findByCode(_code: Byte) = DetailDataType.values().find { it.code == _code }
        }

    }
}